package pzc.io.util;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;

import pzc.lang.BinaryUtils;

public class FileChecker {

	/**
	 * 检测是否是UTF-8
	 * 
	 * @param file
	 * @return
	 * @throws Exception
	 */
	public static final boolean isUTF8(File file) throws Exception {
		boolean ret = false;
		// 检测是否是BOM开头
		ret = isBOM(file);
		if (ret == false) {
			// 若不是BOM开头，则自动检测
			ret = autoDetectUTF8(file);
		} else {
			System.out.println("isBOM : " + file.getAbsolutePath());
		}
		return ret;
	}

	/**
	 * 判断文件是否以BOM开头(典型的UTF-8) : EF BB BF
	 * 
	 * @param file
	 * @return
	 * @throws Exception
	 */
	public static final boolean isBOM(File file) throws Exception {
		String byte1 = "EF";
		String byte2 = "BB";
		String byte3 = "BF";

		boolean ret = false;
		if (file != null && file.exists() && file.length() >= 3) {
			FileInputStream fis = new FileInputStream(file);
			byte[] b = new byte[3];
			fis.read(b, 0, 3);
			fis.close();

			String s1 = Integer.toHexString(Integer.valueOf(b[0]).intValue()).toUpperCase();
			String s2 = Integer.toHexString(Integer.valueOf(b[1]).intValue()).toUpperCase();
			String s3 = Integer.toHexString(Integer.valueOf(b[2]).intValue()).toUpperCase();

			if (s1.endsWith(byte1) && s2.endsWith(byte2) && s3.endsWith(byte3)) {
				ret = true;
			}
		}

		return ret;
	}

	/**
	 * 自动检测是否是UTF-8.
	 * 
	 * <pre>
	 * 检测方法来自网上，出处忘了
	 * </pre>
	 * 
	 * @param file
	 * @return
	 * @throws Exception
	 */
	private static boolean autoDetectUTF8(File file) throws Exception {
		byte[] bs = FileUtils.readFileToByteArray(file);

		byte current_byte = 0;
		byte previous_byte = 0;

		long count_good_utf = 0;
		long count_bad_utf = 0;

		for (int i = 0; i < bs.length; i++) {
			current_byte = bs[i];
			if (i > 0) {
				previous_byte = bs[i - 1];
			}

			if ((current_byte & 0xC0) == 0x80) {
				if ((previous_byte & 0xC0) == 0xC0) {
					count_good_utf++;
				} else if ((previous_byte & 0x80) == 0x0) {
					count_bad_utf++;
				}
			} else if ((previous_byte & 0xC0) == 0xC0) {
				count_bad_utf++;
			}
		}

		boolean ret = false;

		if (count_good_utf >= count_bad_utf) {
			ret = true;
		}

		return ret;
	}

	/**
	 * GB2312 check.
	 * 
	 * <pre>
	 * 判断根据：http://wapedia.mobi/en/GB2312.
	 * EUC-CN is often used as the character encoding (i.e. for external storage) in programs that deal
	 * with GB2312, thus maintaining compatibility with ASCII. Two bytes are
	 * used to represent every character not found in ASCII. The value of the
	 * first byte is from 0xA1-0xF7 (161-247), while the value of the second
	 * byte is from 0xA1-0xFE (161-254). Hence, like UTF-8, it is possible to
	 * check if a byte is part of a two-byte construct when using EUC-CN.
	 * 
	 * 也就是说，除了ASCII字符以外，其他都作为2字节字符保存。
	 * 双字节：第一字节的范围是：0xA1-0xF7 (161-247)
	 *             第二字节的范围是：0xA1-0xFE (161-254)
	 * </pre>
	 * 
	 * @param file
	 * @return
	 * @throws Exception
	 */
	public static final boolean isGB2312(File file) throws Exception {
		List<int[]> ranges = new ArrayList<int[]>();
		ranges.add(new int[] { 0xA1, 0xF7, 0xA1, 0xFE });
		return check2bytesRangeOfCode(file, ranges);
	}

	/**
	 * 
	 * <pre>
	 * firstByte : 0x81 - 0xFE
	 * secondByte : 0x40 - 0xFE
	 * </pre>
	 * 
	 * @param file
	 * @return
	 * @throws Exception
	 */
	public static final boolean isGBK(File file) throws Exception {
		List<int[]> ranges = new ArrayList<int[]>();
		ranges.add(new int[] { 0x81, 0xFE, 0x40, 0xFE });
		return check2bytesRangeOfCode(file, ranges);
	}

	public static final boolean isShift_JIS(File file) throws Exception {
		List<int[]> ranges = new ArrayList<int[]>();
		ranges.add(new int[] { 0x80, 0x9F, 0x40, 0x7E });
		ranges.add(new int[] { 0x80, 0x9F, 0x80, 0xFC });
		ranges.add(new int[] { 0xE0, 0xFC, 0x40, 0x7E });
		ranges.add(new int[] { 0xE0, 0xFC, 0x80, 0xFC });
		return check2bytesRangeOfCode(file, ranges);
	}

	private static final boolean isASCII(byte b) {
		if (b >= 0 && b <= 127) {
			return true;
		} else {
			return false;
		}
	}

	public static final boolean check2bytesRangeOfCode(File file, List<int[]> ranges) throws Exception {
		boolean ret = true;

		byte[] bs = FileUtils.readFileToByteArray(file);
		if (bs == null || bs.length == 0) {
			// 文件内容为空，长度为0
			ret = false;
		} else {
			int firstByte = -1;
			for (int i = 0; i < bs.length; i++) {
				if (firstByte != -1) {
					// 已设置第一字节

					// 设置第二字节
					int secondByte = BinaryUtils.b2iu(bs[i]);

					// 判断是否在范围内
					boolean isInRanges = false;
					for (int[] range : ranges) {
						int firstByteMin = range[0];
						int firstByteMax = range[1];
						int secondByteMin = range[2];
						int secondByteMax = range[3];

						if (firstByte >= firstByteMin && firstByte <= firstByteMax && secondByte >= secondByteMin && secondByte <= secondByteMax) {
							isInRanges = true;
							break;
						}
					}

					if (isInRanges == false) {
						ret = false;
						break;
					}
					// 在范围内的话，继续下一个字符判断

					// 判断结束，第一字节设置-1
					firstByte = -1;
				} else {
					if (isASCII(bs[i])) {
						// 若是ASCII字符，则第一字节置-1
						firstByte = -1;
					} else {
						// 不是ASCII字符，设置第一字节为当前字节
						firstByte = BinaryUtils.b2iu(bs[i]);
					}
				}
			}
		}

		return ret;
	}
}
